using System;
using System.Collections.Generic;
using System.Drawing;
using Microsoft.SmallBasic.Library;
using SmallBasicFun.TicTacToeGame;

namespace SmallBasicFun
{
	[SmallBasicType]
	public class TicTacToe
	{
		private static readonly Board board = new Board();
		private static int animationDelay = 700;
		private static SmallBasicCallback playerO = new RandomPlayer().Play;
		private static SmallBasicCallback playerX = new RandomPlayer().Play;
		private static Status turnLock = Status.Unstarted;

		/// <summary>
		/// Sets the subroutine to be called when it's Player X's turn.
		/// Player X always goes first
		/// </summary>
		/// <example>TicTacToe.TurnForPlayerX = yourComputerPlayer</example>
		public static event SmallBasicCallback TurnForPlayerX
		{
			add { playerX = value; }

			remove { }
		}

		/// <summary>
		/// Sets the subroutine to be called when it's Player O's turn.
		/// Player O always goes second
		/// </summary>
		/// <example>TicTacToe.TurnForPlayerO = yourComputerPlayer</example>
		public static event SmallBasicCallback TurnForPlayerO
		{
			add { playerO = value; }

			remove { }
		}


		/// <summary>
		/// Places a piece on a square (1-9). Which one is best? That's up to you.
		/// </summary>
		/// <example>TicTacToe.PlayPiece(5)</example>
		/// <param name="square"> Which Square to Play in (1-9)</param>
		public static void PlayPiece(Primitive square)
		{
			switch (turnLock)
			{
				case Status.Unstarted:
					throw new PlayException("The Game hasn't started yet, PlayPiece({0}) is illegal", square);
				case Status.Played:
					throw new PlayException("You can't play more than once per turn, PlayPiece({0}) is illegal", square);
				case Status.GameOver:
					throw new PlayException("You can't play after the game is over, PlayPiece({0}) is illegal", square);
			}


			Point p = board.GetXYBySquare(square);
			if (!board.IsValid(p.X, p.Y))
			{
				throw new PlayException("You can't go play a piece on square #{0} !", square);
			}
			board.PlacePiece(board.CurrentPlayer, p.X, p.Y);
			turnLock = board.IsGameOver() ? Status.GameOver : Status.Played;
		}

		/// <summary>
		/// Is [2,3] square #8 or #6? Too hard to find out? Not to worry, this method will do the math for you!
		/// </summary>
		/// <example>square = TicTacToe.GetSquareByXY(2,3)</example>
		/// <param name="x">The X location (1-3)</param>
		/// <param name="y">The Y location (1-3)</param>
		/// <returns> The corresponding square location (1-9)</returns>
		public static Primitive GetSquareByXY(Primitive x, Primitive y)
		{
			return board.GetSquareByXY(x, y);
		}

		/// <summary>
		/// You can't play in a square that's already taken, so it's good to ask first. This will tell you if it's too late.
		/// </summary>
		/// <example>taken = TicTacToe.IsEmpty(3)</example>
		/// <param name="square"> The Square (1-9) you want to know about</param>
		/// <returns>true if empty, false if not</returns>
		public static Primitive IsEmpty(Primitive square)
		{
			Point p = board.GetXYBySquare(square);
			return board.IsValidPlay(p.X, p.Y);
		}
		/// <summary>
		/// Get ready! Get Set! Go! Time to start the game, may the program win!
		/// </summary>
		/// <example>TicTacToe.Start()</example>
		public static void Start()
		{
			bool x = true;
			var draw = new DrawTicTacToe();
			try
			{
				while (!board.IsGameOver())
				{
					string start = board.GetCurrentTurn();
					turnLock = Status.Waiting;
					if (x)
					{
						playerX();
					}
					else
					{
						playerO();
					}
					string end = board.GetCurrentTurn();
					if (start == end)
					{
						throw new PlayException("Player " + start + " did not move!");
					}
					x = !x;
					draw.UpdateDisplay(board);
					Program.Delay(animationDelay);
				}
			}
			finally
			{
				draw.UpdateDisplay(board);
			}
		}

		public static void SetAnimationDelay(int value)
		{
			animationDelay = value;
		}

		private enum Status
		{
			Unstarted,
			Waiting,
			Played,
			GameOver
		}
	}

	public class PlayException : Exception
	{
		public PlayException(string format, params object[] p)
			: base(string.Format("I call shenanigans!\r\n" + format, p))
		{
		}
	}

	internal class RandomPlayer
	{
		public void Play()
		{
			var squares = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9};
			while (squares.Count > 0)
			{
				int s = squares.GetRandom();
				squares.Remove(s);
				if (TicTacToe.IsEmpty(s))
				{
					TicTacToe.PlayPiece(s);
					break;
				}
			}
		}
	}

	public class DrawTicTacToe
	{
		private readonly Primitive horizontalLine1;
		private readonly Primitive horizontalLine2;
		private readonly List<Primitive> Os = new List<Primitive>();
		private readonly Primitive vecticalLine1;
		private readonly Primitive vecticalLine2;
		private readonly List<Primitive> Xs = new List<Primitive>();
		private int xTop = 100;
		private int yTop = 100;

		public DrawTicTacToe()
		{
			horizontalLine1 = ShapeMaker.CreateRectangle(300, 10);
			horizontalLine2 = ShapeMaker.CreateRectangle(300, 10);
			vecticalLine1 = ShapeMaker.CreateRectangle(10, 300);
			vecticalLine2 = ShapeMaker.CreateRectangle(10, 300);
			for (int i = 0; i < 9; i++)
			{
				ShapeMaker.SetColorForNextShape(Colors.Blue);
				Xs.Add(ShapeMaker.CreateRectangle(80, 80));
				ShapeMaker.SetColorForNextShape(Colors.Red);
				Os.Add(ShapeMaker.CreateCircle(40));
			}
		}

		public void UpdateDisplay(Board board)
		{
			DrawLines();
			DrawBoard(board);
			DrawWinner(board);
		}

		private void DrawWinner(Board board)
		{
			if (board.IsGameOver())
			{
				GraphicsWindow.BrushColor = GetColorForWinner(board.GetWinner());
				GraphicsWindow.FontName = Fonts.ArialBlack;
				GraphicsWindow.FontSize = 44;

				string text = board.IsTieGame() ? "Tie game " : board.GetWinner() + " Has Won";
				ProgramWindow.DrawText(text, 10, 10);
			}
		}

		private Primitive GetColorForWinner(string winner)
		{
			if (winner == "X")
			{
				return Colors.Blue;
			}
			else if (winner == "Y")
			{
				return Colors.Red;
			}
			return Colors.Gray;
		}

		private void DrawBoard(Board board)
		{
			int xCount = 0;
			int oCount = 0;
			for (int x = 0; x < 3; x++)
			{
				for (int y = 0; y < 3; y++)
				{
					string cell = board.GetCell(x, y);
					if (cell == "X")
					{
						ShapeMaker.CenterShapeAt(Xs[xCount++], xTop + (100*x) + 50, yTop + (100*y) + 50);
					}
					if (cell == "O")
					{
						ShapeMaker.CenterShapeAt(Os[oCount++], xTop + (100*x) + 50, yTop + (100*y) + 50);
					}
				}
			}
		}

		private void DrawLines()
		{
			ShapeMaker.CenterShapeAt(horizontalLine1, 150 + xTop, 100 + yTop);
			ShapeMaker.CenterShapeAt(horizontalLine2, 150 + xTop, 200 + yTop);
			ShapeMaker.CenterShapeAt(vecticalLine1, 100 + xTop, 150 + yTop);
			ShapeMaker.CenterShapeAt(vecticalLine2, 200 + xTop, 150 + yTop);
		}
	}
}